#include "PotentialMap.h"
#include "MathHelper.h"
#include "ScoutingManager.h"
#include <BWAPI.h>
#include <BWTA.h>
#include <math.h>
#include "Source/StarcraftAI.h"
#include "ReinforcementLearning.h"

using namespace BWAPI;
BWAPI::Unit* _myUnit;
#define PI 3.14159265
#define INF 100000000

// FOR POTENTIAL MAP
double _deltaX = 0;
double _deltaY = 0;

//THE TARGET VALUES
double _alphaTarget = 1; //used to adjust the strength of the target potential
double _sizeOfTarget = 1000; //Known as S - Adjust or learn.. This denoted how fair away the goal has an effect
int _targetSize = 0; //known as r. Is calculated in the function

//ATTACKING BULDING VALUES
double _alphaAttackingBuldings = 1; //used to adjust the strength of the building potential
int _buildingRange = 0; //Known as s - depends on each buling. (its the range!)
int _buildingsSize = 32; //Known as r - ADJUST

//UNITS THAT CAN ATTACK US
double _alphaAttackingUnits = 10; //used to adjust the strength of the unit potential
int _unitRange = 0; //Known as s - depends on each unit. (its the range!)
int _unitSize = 0; //Known as r - depends on each unit. (its the unit size!)

int PotentialMap::CalculateSizeModifier(BWAPI::UnitSizeType unitSize)
{
	int size = 0;
	if(unitSize.getName() == "Small")
	{
		size = 4;
	} else if(unitSize.getName() == "Medium")
	{
		size = 5;
	} else if(unitSize.getName() == "Large")
	{
		size = 6;
	}
	return size;
}
int PotentialMap::CalculateRangeModifier(BWAPI::Unit* unit,bool isAirUnit)
{
	if(unit->getType() == BWAPI::UnitTypes::Zerg_Hydralisk)
	{
		if(unit->getUpgradeLevel(BWAPI::UpgradeTypes::Grooved_Spines))
		{
			return 32;
		}
		return 0;
	}
	if(unit->getType() == BWAPI::UnitTypes::Protoss_Dragoon)
	{
		if(unit->getUpgradeLevel(BWAPI::UpgradeTypes::Singularity_Charge))
		{
			return 64;
		}
		return 0;
	}
	if(unit->getType() == BWAPI::UnitTypes::Terran_Marine)
	{
		if(unit->getUpgradeLevel(BWAPI::UpgradeTypes::U_238_Shells))
		{
			return 32;
		}
		return 0;
	}
	if(unit->getType() == BWAPI::UnitTypes::Terran_Goliath)
	{
		if(unit->getUpgradeLevel(BWAPI::UpgradeTypes::Charon_Boosters) && isAirUnit)
		{
			return 96;
		}
		return 0;
	}

	return 0;
}
void PotentialMap::Initialization(BWAPI::Unit* myUnit)
{
	_myUnit = myUnit;
	_deltaX = 0;
	_deltaY = 0;

	/*
	Load from file later
	_alphaTarget
	_alphaAttackingBuldings

	*/

	

}
void PotentialMap::HandleBuldings(bool isAirUnit)
{
	//FINDING ALL BULDINGS KNOWN AND ALL UNITS KNOWN.
	std::list<BWAPI::Unit*> enemieBuldings;
	if(isAirUnit)
	{
		for each (BWAPI::Unit* building in ScoutingManager::GetInstance()->enemyBuildings[BWAPI::UnitTypes::Terran_Missile_Turret])
			if(building->exists())
				enemieBuldings.push_back(building);

		for each (BWAPI::Unit* building in ScoutingManager::GetInstance()->enemyBuildings[BWAPI::UnitTypes::Protoss_Photon_Cannon])
			if(building->exists())
				enemieBuldings.push_back(building);

		for each (BWAPI::Unit* building in ScoutingManager::GetInstance()->enemyBuildings[BWAPI::UnitTypes::Zerg_Spore_Colony])
			if(building->exists())
				enemieBuldings.push_back(building);

		for each (BWAPI::Unit* building in ScoutingManager::GetInstance()->enemyBuildings[BWAPI::UnitTypes::Terran_Bunker])
			if(building->exists())
				enemieBuldings.push_back(building);

	}
	else
	{
		for each (BWAPI::Unit* building in ScoutingManager::GetInstance()->enemyBuildings[BWAPI::UnitTypes::Protoss_Photon_Cannon])
			if(building->exists())
				enemieBuldings.push_back(building);

		for each (BWAPI::Unit* building in ScoutingManager::GetInstance()->enemyBuildings[BWAPI::UnitTypes::Zerg_Sunken_Colony])
			if(building->exists())
				enemieBuldings.push_back(building);

		for each (BWAPI::Unit* building in ScoutingManager::GetInstance()->enemyBuildings[BWAPI::UnitTypes::Terran_Bunker])
			if(building->exists())
				enemieBuldings.push_back(building);
	}
	for each (BWAPI::Unit* building in enemieBuldings)
	{

		double magnitude = sqrt(pow(_myUnit->getVelocityX(),2)+pow(_myUnit->getVelocityY(),2)) * 8;
		int modifyer = (int)magnitude + 48;
		//Broodwar->printf("modifyer %d ,  %d",modifyer,Broodwar->getFrameCount());
		
		// its a building its a constant.
		if(building->getType() == BWAPI::UnitTypes::Terran_Bunker)
		{
			std::set<Unit*> loadedUnits = building->getLoadedUnits();

			_buildingRange = 0;

			for each(BWAPI::Unit* unit in loadedUnits)
			{
				int range = 0;
				if(isAirUnit)
					range = unit->getType().airWeapon().maxRange() + PotentialMap::CalculateRangeModifier(unit,isAirUnit);
				else
					range = unit->getType().groundWeapon().maxRange() + PotentialMap::CalculateRangeModifier(unit,isAirUnit);

				if(range > _buildingRange)
					_buildingRange = range;
			}
		}
		else
		{
			if(isAirUnit)
				_buildingRange = building->getType().airWeapon().maxRange() + modifyer;
			else
				_buildingRange = building->getType().groundWeapon().maxRange() + modifyer;
		}
		double distanceBetweenUnitAndTarget = sqrt(pow((double)(building->getPosition().x() - _myUnit->getPosition().x()),2) + pow((double)(building->getPosition().y() - _myUnit->getPosition().y()),2)); //known as d
		double angleBetweenUnitAndTarget = atan2((double)(building->getPosition().y() - _myUnit->getPosition().y()),(double)(building->getPosition().x() - _myUnit->getPosition().x())); //known as gamma - its in radians

		if(distanceBetweenUnitAndTarget < _buildingsSize)
		{
			double xAdjust = cos(angleBetweenUnitAndTarget);
			double yAdjust = sin(angleBetweenUnitAndTarget);
			if(xAdjust > 0)
				_deltaX -= INF;
			else
				_deltaX += INF;
			
			if(yAdjust > 0)
				_deltaY -= INF;
			else
				_deltaY += INF;
						
		} 
		else if((_buildingsSize <= distanceBetweenUnitAndTarget) && (distanceBetweenUnitAndTarget <= _buildingRange + _buildingsSize))
		{
			_deltaX -= _alphaAttackingBuldings*(_buildingRange + _buildingsSize - distanceBetweenUnitAndTarget)*cos(angleBetweenUnitAndTarget);
			_deltaY -= _alphaAttackingBuldings*(_buildingRange + _buildingsSize - distanceBetweenUnitAndTarget)*sin(angleBetweenUnitAndTarget);
		} 
		else if(distanceBetweenUnitAndTarget > _buildingRange + _buildingsSize)
		{
			//Add zero
		}
		Broodwar->drawCircleMap(building->getPosition().x(),building->getPosition().y(),building->getType().airWeapon().maxRange() + modifyer,BWAPI::Colors::Orange,false);
		Broodwar->drawCircleMap(building->getPosition().x(),building->getPosition().y(),building->getType().airWeapon().maxRange(),BWAPI::Colors::Green,false);
	}
}
void PotentialMap::HandleUnits(bool isAirUnit)
{
	//Going trough all of the to add negative potential to all the tiles in range..
	for each (BWAPI::UnitType type in BWAPI::UnitTypes::allUnitTypes())
	{
		if(!type.isBuilding())
		{
			for each (BWAPI::Unit* unit in ScoutingManager::GetInstance()->enemyUnits[type])
			{
				if(unit->exists() && unit->isVisible())
				{
					if((!isAirUnit && (unit->getType().groundWeapon() != BWAPI::WeaponTypes::None))|| (isAirUnit && (unit->getType().airWeapon() != BWAPI::WeaponTypes::None)))
					{
						_unitSize = PotentialMap::CalculateSizeModifier(unit->getType().size()) * 8 ;
						double modifyer =  _myUnit->getType().turnRadius() + _unitSize + PotentialMap::CalculateRangeModifier(unit,isAirUnit);
						double dmg = 100;

						if(isAirUnit)
						{
							_unitRange = (unit->getType().airWeapon().maxRange())+(int)modifyer;
							dmg = unit->getType().airWeapon().damageAmount()*(30 / unit->getType().airWeapon().damageCooldown());
						}
						else
						{
							_unitRange = (unit->getType().groundWeapon().maxRange())+(int)modifyer;
							dmg = unit->getType().groundWeapon().damageAmount()*(30 / unit->getType().groundWeapon().damageCooldown());
						}

						double distanceBetweenUnitAndTarget = sqrt(pow((double)(unit->getPosition().x() - _myUnit->getPosition().x()),2) + pow((double)(unit->getPosition().y() - _myUnit->getPosition().y()),2)); //known as d
						double angleBetweenUnitAndTarget = atan2((double)(unit->getPosition().y() - _myUnit->getPosition().y()),(double)(unit->getPosition().x() - _myUnit->getPosition().x())); //known as gamma - its in radians

						if(distanceBetweenUnitAndTarget < _unitSize)
						{
							double xAdjust = cos(angleBetweenUnitAndTarget);
							double yAdjust = sin(angleBetweenUnitAndTarget);
							if(xAdjust > 0)
								_deltaX -= INF;
							else
								_deltaX += INF;
							
							if(yAdjust > 0)
								_deltaY -= INF;
							else
								_deltaY += INF;

						} 
						else if((_unitSize <= distanceBetweenUnitAndTarget) && (distanceBetweenUnitAndTarget <= _unitRange + _unitSize))
						{
							_deltaX -= _alphaAttackingUnits*(_unitRange*dmg - distanceBetweenUnitAndTarget)*cos(angleBetweenUnitAndTarget);
							_deltaY -= _alphaAttackingUnits*(_unitRange*dmg - distanceBetweenUnitAndTarget)*sin(angleBetweenUnitAndTarget);
						} 

						//Broodwar->drawCircleMap(unit->getPosition().x(),unit->getPosition().y(),unit->getType().airWeapon().maxRange() + (int)modifyer,BWAPI::Colors::Orange,false);
						//Broodwar->drawCircleMap(unit->getPosition().x(),unit->getPosition().y(),unit->getType().airWeapon().maxRange(),BWAPI::Colors::Green,false);						
					}
				}
			}
		}
	}
}


void PotentialMap::HandleTarget(BWAPI::Unit* target)
{
	//stay just witin range of target (FOR DEBUG ITS THE CLOSES UNIT)
	
	double targetX = target->getPosition().x();
	double targetY = target->getPosition().y();
	double distanceBetweenUnitAndTarget = sqrt(pow((double)_myUnit->getPosition().x() - targetX,2)+pow((double)_myUnit->getPosition().y() - targetY,2)); //known as d
	double angleBetweenUnitAndTarget = atan2((targetY - (double)_myUnit->getPosition().y()),(targetX - (double)_myUnit->getPosition().x())); //known as gamma - its in radians
	
	_targetSize = PotentialMap::CalculateSizeModifier(target->getType().size()) * 8;
	if(distanceBetweenUnitAndTarget < _targetSize)
	{
		_deltaX = 0;
		_deltaY = 0;
	} 
	else if((_targetSize <= distanceBetweenUnitAndTarget) && (distanceBetweenUnitAndTarget <= _sizeOfTarget + _targetSize))
	{
		_deltaX += _alphaTarget * (distanceBetweenUnitAndTarget) * cos(angleBetweenUnitAndTarget);
		_deltaY += _alphaTarget * (distanceBetweenUnitAndTarget) * sin(angleBetweenUnitAndTarget);
	} 
	else if(distanceBetweenUnitAndTarget > _sizeOfTarget + _targetSize)
	{
		_deltaX += _alphaTarget * cos(angleBetweenUnitAndTarget);
		_deltaY += _alphaTarget * sin(angleBetweenUnitAndTarget);
	}

	//Broodwar->drawCircleMap((int)targetX,(int)targetY,_targetSize,BWAPI::Colors::Green,false);

	
	
}
void PotentialMap::ExecuteTactic(BWAPI::Unit* myUnit,std::set<BWAPI::Unit*> theSquad, BWAPI::Unit* target, bool targetExist)
{
	/*
		TODO IN POTENTIALFIELDS:
		TODO: BALANCE THE TARGET ATTRACTIVENESS AND THE UNIT AVOIDNESS FOORMULAS
		TODO: Handle attack so its nice

		TODO OTHERS:
		TODO: Intelligent targeting.. If the closes unit cant attack air.. Maybe attack something else
		TODO: A* for path finding.
	*/
	PotentialMap::Initialization(myUnit);
	PotentialMap::HandleBuldings(myUnit->getType().isFlyer());
	PotentialMap::HandleUnits(myUnit->getType().isFlyer());

	if(targetExist)
		PotentialMap::HandleTarget(target);

	//MOVEMENT WORKING!
	double velocity = sqrt(pow(_deltaX,2)+pow(_deltaY,2)) * 0.5; // Multiplaying with a low number for smooth movement - but to low will cause it to move laggish
	double angle = atan2(_deltaY,_deltaX);
	int newX = myUnit->getPosition().x()+(int)(cos(angle)*velocity);
	int newY = myUnit->getPosition().y()+(int)(sin(angle)*velocity);
	
	if(targetExist) //in case no target exist
	{
		if(myUnit->getAirWeaponCooldown() != 0  || _myUnit->isAttackFrame())
		{
			//Broodwar->printf("DONT ATTACK TARGET %d",Broodwar->getFrameCount());
			myUnit->move(BWAPI::Position(newX,newY));
		}
		else
		{

			myUnit->attack(target);
			
			//myUnit->move(BWAPI::Position(newX,newY));
		}
		
	}
	else
	{
		myUnit->move(BWAPI::Position(newX,newY));
	}

	/*
		The code below may be good for units with shorter range then us
	*/ 

	/*
	if(!firstUnit) //in case no target exist
	{
		_unitSize = PotentialMap::CalculateSizeModifier(target->getType().size());
		int enemySpeed = (int)sqrt(pow(target->getVelocityX(),2)+pow(target->getVelocityY(),2));
		double modifyer = _myUnit->getType().turnRadius() + _unitSize + PotentialMap::CalculateRangeModifier(target,true) + enemySpeed*2;
		int calc = target->getType().airWeapon().maxRange() + modifyer;

		if(myUnit->getDistance(target) <= calc)
		{
			myUnit->move(BWAPI::Position(newX,newY));
			Broodwar->printf("MOVE %d",Broodwar->getFrameCount());
		}
		else
		{

			if(myUnit->getAirWeaponCooldown() != 0  || _myUnit->isAttackFrame())
			{
				//Broodwar->printf("DONT ATTACK TARGET %d",Broodwar->getFrameCount());
				myUnit->move(BWAPI::Position(newX,newY));
			}
			else
			{
				myUnit->attack(target);
				
				//myUnit->move(BWAPI::Position(newX,newY));
			}
		}
	}
	else
	{
		myUnit->move(BWAPI::Position(newX,newY));
	}*/
	
}